﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Text;

namespace AsyncEvtEcho
{
    sealed class SvrResponse : IDisposable
    {
        // *************************************************************** //
        #region [ member fields ]

        private readonly Socket m_socket;

        /// <summary>
        /// because if the socket has already been closed, at that time, access "RemoteEndPoint" of that error or closed socket
        /// will throw exception, so we have to cache this endpoint information here
        /// </summary>
        private readonly EndPoint m_clientEndpnt;

        private readonly HeadBodyRecver m_recvFSM;

        #endregion

        // *************************************************************** //
        #region [ constructor ]

        public SvrResponse(Socket socket)
        {
            m_socket = socket;
            m_clientEndpnt = m_socket.RemoteEndPoint;
            m_recvFSM = new HeadBodyRecver();
        }

        #endregion

        // *************************************************************** //
        #region [ destructor ]

        private bool m_disposed;

        ~SvrResponse()
        {
            Dispose(false);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        private void Dispose(bool disposing)
        {
            if (m_disposed)
                return;

            try
            {
                if (disposing)
                {
                    m_socket.Shutdown(SocketShutdown.Both);
                    m_socket.Close();
                }
            }
            finally
            {
                m_disposed = true;
            }
        }

        #endregion

        // *************************************************************** //
        #region [ public APIs ]

        public EndPoint ClientEndPoint { get { return m_clientEndpnt; } }

        public void StartReceive(SocketAsyncEventArgs evtArgs, Action<SocketAsyncEventArgs> recvCallback)
        {
            m_recvFSM.StartRecvSocket(m_socket, evtArgs, recvCallback);
        }

        /// <summary>
        /// returning true represents "whole body received", otherwise returns false
        /// </summary>
        public bool OnReceived(byte[] buffer, int offset, int count)
        {
            return m_recvFSM.Receive(buffer, offset, count);
        }

        public void StartReply(SocketAsyncEventArgs evtargs, Action<SocketAsyncEventArgs> sendCallback)
        {
            // ---------------------------- input
            Tuple<Header, byte[]> requestPacket = m_recvFSM.LastPacket;
            string oriMessage = Encoding.ASCII.GetString(requestPacket.Item2);

            // ---------------------------- process
            string rsltMessage = null;
            switch (requestPacket.Item1.Command)
            {
                case Utility.CmdToUpper:
                    rsltMessage = oriMessage.ToUpper();
                    break;

                case Utility.CmdToLower:
                    rsltMessage = oriMessage.ToLower();
                    break;

                default:
                    throw new ArgumentOutOfRangeException("un-recognized command");
            }

            // ---------------------------- output
            byte[] rsltBuffer = rsltMessage.MakePacket(requestPacket.Item1.Command).CombineBinary();
            evtargs.SetBuffer(rsltBuffer, 0, rsltBuffer.Length);

            if (!m_socket.SendAsync(evtargs))
                sendCallback(evtargs);
        }

        #endregion
    }
}
